/* eslint-disable no-bitwise */
/**
 * 默认报告样式
 *
 * Created by snowingsea on 2021/01/19.
 */
const fs = require('fs-extra');
const path = require('path');
const moment = require('moment');

const tests = require('./../../../tests');
const utility = require('./../../../utility');
const ParentReporter = require('./../default-report/index');
const BaseReporter = require('./../base-report');
const DataAdapter = require('./data-adapter');
const TargetMachineDataReport = require('./target-machine-data-adapter');
const { collectCoverageKind } = require('../../../project-versions');
const ProjectVersionSpliter = require('./../../project-version-spliter');

class DefaultReporter extends ParentReporter {
  constructor(versionId, locale, version = null, project = null, creator = null, outputDir = null, format, complete) {
    super(versionId, locale, version, project, creator, outputDir, format, complete);
    this.setDataAdapter(new DataAdapter());
    this.baseDir = __dirname;
  }

  setTargetMachineDataSource(data) {
    this.setDataAdapter(new TargetMachineDataReport(data.filesMap, data.functionsMap, data.testcasesMap, this.version, this.project));
  }

  /**
   * 生成测试报告总览
   * @returns {Promise}
   */
  generateSummary() {
    const summary = {
      reportTime: moment().format('YYYY/MM/DD HH:mm:ss'),
      isIntegrationTest: this.isIntegrationTest(),
      reportName: this.getReportName(),
      coverageRange: !this.version.collectCoverageKind ? collectCoverageKind.all : this.version.collectCoverageKind,
    };
    const extensions = Object.values(this.version.languages)
      .reduce((arr, item) => arr.concat(item.suffixes), []);
    return this.getDataAdapter().getVersionOverview(this.versionId)
      .then((overview) => {
        const enabledCoverageKind = [];
        const enabledUTCoverageKind = [];
        const enabledITCoverageKind = [];
        ['statement', 'branch', 'mcdc', 'condition', 'decision', 'invoke', 'invokedFunc'].forEach((key) => {
          if (this.version.coverages & tests.coverageKind[key]) {
            const o = {
              key,
              coverageKey: `${key}Coverage`,
              totalKey: `${key}Total`,
              coveredKey: `${key}Covered`,
              name: this.i18n.__(`tests.${key}Coverage`),
            };
            enabledCoverageKind.push(o);
            if (['statement', 'branch', 'mcdc'].includes(key)) {
              enabledUTCoverageKind.push(o);
            } else {
              enabledITCoverageKind.push(o);
            }
          }
          overview[`${key}Coverage`] = '-';
          if (overview[`${key}Total`]) {
            overview[`${key}Coverage`] = overview[`${key}Covered`] / overview[`${key}Total`];
            overview[`${key}Coverage`] = `${Math.round(overview[`${key}Coverage`] * 10000) / 100}%`;
          }
        });
        summary.overview = overview;
        summary.enabledCoverageKind = enabledCoverageKind;
        summary.enabledITCoverageKind = enabledITCoverageKind;
        summary.enabledUTCoverageKind = enabledUTCoverageKind;
        return this.getDataAdapter().getTestResultOverview(this.versionId);
      })
      .then((testResultOverview) => {
        const testsOverview = {
          succeed: 0,
          nonStrictSucceed: 0,
          failed: 0,
          noResult: 0,
          exception: 0,
          codeDefect: 0,
          unknown: 0,
        };
        Object.keys(testResultOverview).forEach((key) => {
          Object.keys(testResultOverview[key]).forEach((testResultKind) => {
            testResultKind = Number(testResultKind);
            const testResultKey = BaseReporter.getTestResultKindKey(testResultKind);
            testsOverview[testResultKey] += testResultOverview[key][testResultKind];
          });
        });
        testsOverview.total = Object.keys(testsOverview)
          .reduce((total, key) => total + testsOverview[key], 0);
        summary.testsOverview = testsOverview;
        return this.getDataAdapter().getFunctionCoverageDistribution(this.versionId);
      })
      .then((functionsCoverage) => {
        summary.functionsCoverage = functionsCoverage;
        summary.functionsCoverage.interval = 25;
        return Promise.resolve();
      })
      .then(() => this.getDataAdapter().getFilesOverview(this.versionId, extensions))
      .then(({ filesOverview, fileStatusCount }) => {
        summary.sourceFilesOverview = ParentReporter.handleOverview(
          filesOverview,
          summary.enabledCoverageKind
        );
        summary.sourceFileStatusCount = fileStatusCount.reduce((pre, status) => {
          pre[status.status] = status.count;
          return pre;
        }, {});
        return this.getDataAdapter().getFunctionsOverview(this.versionId, { pageSize: 999999 });
      })
      .then(([functionsOverview, funcResultStatistic]) => {
        summary.functionResultsStatistic = funcResultStatistic;
        summary.functionsOverview = ParentReporter.handleOverview(
          functionsOverview,
          summary.enabledCoverageKind
        );
        if (!this.isIntegrationTest()) {
          return Promise.resolve();
        }
        return utility.arrayChunkOperation(summary.functionsOverview, 20, (func) => this.getDataAdapter().getIntegratedFuncs(func._id, true)
          .then((integratedFuncs) => {
            func.integrationFunctions = integratedFuncs
              .map((relation, index) => ({ name: `${relation.toFuncName}${index === integratedFuncs.length - 1 ? '' : ','}` }));
            func.invokedFuncCoverage = func.invokedFuncTotal ? `${Math.round((func.invokedFuncCovered / func.invokedFuncTotal) * 10000) / 100}%` : '-';
            func.invokeCoverage = func.invokeTotal ? `${Math.round((func.invokeCovered / func.invokeTotal) * 10000) / 100}%` : '-';
          }));
      })
      .then(() => this.getDataAdapter().getVersionTests(this.versionId))
      .then((versionTests) => {
        summary.testCasesOverview = [];
        const testsMap = new Map();
        versionTests.forEach((test) => {
          if (!testsMap.has(test.functionId.toString())) {
            testsMap.set(test.functionId.toString(), []);
          }
          testsMap.get(test.functionId.toString()).push({
            name: `用例${test.number}`,
            result: this.i18n.__(`tests.testResult.${BaseReporter.getTestResultKindKey(test.resultKind)}`),
            kind: test.kind === tests.testKind.modified ? '手动创建' : '自动生成',
            number: test.number,
            identifierNumber: test.identifierNumber,
            userDefinedFields: test.userDefinedFields,
          });
        });
        summary.functionsOverview.map((item, index) => {
          item.index = index + 1;
          if (item.resultKind) {
            item.resultKind = this.i18n.__(`tests.functionResult.${item.resultKind}`);
          }
          if (testsMap.has(item._id.toString())) {
            testsMap.get(item._id.toString()).forEach((test, index) => {
              test.functionName = item.functionName;
              test.testCount = item.testCount;
              test.filePath = item.filePath;
              test.fileName = item.fileName;
              test.identifierRule = this.version.identifierRule;
              if (index === 0) {
                test.showFunc = true;
              }
              test.identifier = tests.getTestIdentifier(test).replace(item.functionName, `${item.functionName}_${test.number}_`);
              if (test.userDefinedFields && utility.isArrayType(test.userDefinedFields)) {
                test.userDefinedFields.forEach((userDefinedField) => {
                  if (userDefinedField.name === '用例对应需求编号') {
                    test.requirementNo = userDefinedField.value;
                  }
                });
              }
              delete test.identifierRule;
              summary.testCasesOverview.push(test);
            });
            item.tests = testsMap.get(item._id.toString());
          }
          return item;
        });
      })
      .then(() => Promise.all([
        this.generateCoverageDistributionImage(
          summary.functionsCoverage,
          this.getImageSize().coverageDistributionImage
        ),
        this.generateTestCaseResultsImage(
          summary.testsOverview,
          this.getImageSize().testCaseResultsImage
        ),
        this.generateFunctionResultsImage(
          summary.functionResultsStatistic,
          this.getImageSize().functionResultsImage
        ),
      ]))
      .then(([coverageDistributionImage, testCaseResultsImage, functionResultsImage]) => {
        summary.coverageDistributionImage = coverageDistributionImage.output;
        summary.testCaseResultsImage = testCaseResultsImage.output;
        summary.functionResultsImage = functionResultsImage.output;
        return this.generateSummaryReport(
          summary,
          path.join(
            this.outputDir,
            this.project.projectName,
            this.version.versionName,
            '测试结果',
            'summary'
          )
        )
          .then(() => this.generateTestCaseSummaryReport(
            summary,
            path.join(
              this.outputDir,
              this.project.projectName,
              this.version.versionName,
              '测试用例',
              'summary'
            )
          ))
          .then(() => Promise.all([
            fs.remove(coverageDistributionImage.output),
            fs.remove(testCaseResultsImage.output),
            fs.remove(functionResultsImage.output),
          ]));
      });
  }

  handleFile(file, versionDir) {
    return new Promise((resolve, reject) => {
      let reportPath = `${path.join(versionDir, '测试结果', path.dirname(file.path), file.fileName)}`;
      ProjectVersionSpliter.prototype.handleFile.call(this, file, versionDir)
        .then((res) => {
          const { typeSystem, functions, resultCount } = res;
          if (!resultCount) {
            return Promise.resolve();
          }
          const fileSummary = ParentReporter.getSummaryOfFile(functions);
          const functionDetail = this.getFunctionDetailsOfFile(typeSystem, functions);
          return fs.ensureDir(path.dirname(reportPath))
            .then(() => this.generateFileReport(
              {
                fileName: file.fileName,
                summary: fileSummary,
                functions: functionDetail,
                isIntegrationTest: this.isIntegrationTest(),
                reportName: this.getReportName(),
              },
              reportPath
            ))
            .then(() => {
              functionDetail.forEach((func) => {
                if (!func.tests.length) {
                  return;
                }
                func.tests.forEach((test) => {
                  if (!test.udfs || !utility.isArrayType(test.udfs)) {
                    return;
                  }
                  for (let i = 0; i < test.udfs.length; i += 1) {
                    if (test.udfs[i].name !== '用例对应需求编号' && test.udfs[i].name !== '测试说明') {
                      test.udfs.splice(i, 1);
                      i -= 1;
                    }
                  }
                  test.identifier = test.identifier.replace(func.functionName, `${func.functionName}_${test.index}_`);
                });
              });
              reportPath = `${path.join(versionDir, '测试用例', path.dirname(file.path), file.fileName)}`;
              return fs.ensureDir(path.dirname(reportPath))
                .then(() => this.generateTestCaseFileReport(
                  {
                    fileName: file.fileName,
                    summary: fileSummary,
                    functions: functionDetail,
                    isIntegrationTest: this.isIntegrationTest(),
                    reportName: this.getReportName(),
                  },
                  reportPath
                ));
            });
        })
        .then(resolve)
        .catch(reject);
    });
  }

  spliteByFile(callback = null) {
    return new Promise((resolve, reject) => {
      const result = {};
      this.spliteByProjectVersion(
        (versionDir) => fs.ensureDir(path.join(versionDir, '测试结果'))
          .then(() => fs.ensureDir(path.join(versionDir, '测试用例')))
      )
        .then(({ projectDir, versionPath }) => {
          result.projectDir = projectDir;
          result.versionPath = versionPath;
          result.versionDir = versionPath;
          return this.getDataAdapter().getFiles(this.versionId);
        })
        .then((resFiles) => this.handleFiles(resFiles, result.versionDir, callback))
        .then(() => {
          resolve(result);
        })
        .catch(reject);
    });
  }

  generateTestCaseSummaryReport(data, output) {
    let template = 'testcase-summary-template';
    if (this.isIntegrationTest()) {
      template = `${template}-it`;
    }
    return this.generateReport(template, data, output, { isSummary: true });
  }

  generateTestCaseFileReport(data, output) {
    return this.generateReport('testcase-file-template', data, output);
  }
}

/**
 * Export model definition object.
 */
module.exports = DefaultReporter;
